//go:build windows

package lib

import (
	"archive/zip"
	"bufio"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli/v2"
	"golang.org/x/sys/windows/svc"
	"golang.org/x/sys/windows/svc/mgr"
	"io"
	"miao-client-plugins/model"
	"miao-client-plugins/utils"
	"miao-client-plugins/utils/cmdutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// 1. 获取更新信息
// 2. 下载文件
// 3. 检查下载文件的MD5
// 4. 解压
// 5. 清理现场

var (
	BuildTime string
	Version   = "1.0.0"
)

func Do() {
	app := &cli.App{
		Name:        "update-plugin",
		Version:     fmt.Sprintf("%s build:%s", Version, BuildTime),
		UsageText:   "eg: miao client 更新插件",
		Description: "为 miao client 提供更新支持",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:     "uri",
				Category: "安装包的下载地址",
				Aliases:  []string{"u"},
			},
			&cli.StringFlag{
				Name:     "md5",
				Category: "安装包的MD5",
				Aliases:  []string{"m"},
			},
			&cli.StringFlag{
				Name:     "target",
				Category: "安装包zip文件的释放路径,默认在当前文件夹下的plugin下",
				Aliases:  []string{"t"},
			},
			&cli.StringFlag{
				Name:     "remove",
				Category: "删除插件,参数为插件名称，例如：file",
				Aliases:  []string{"r"},
			},
			&cli.StringFlag{
				Name:    "list",
				Usage:   "列出插件清单，这里指 plugin 目录下的文件清单",
				Aliases: []string{"l"},
			},
			&cli.StringFlag{
				Name:  "file",
				Usage: "获取特定文件的MD5",
			},
			&cli.StringFlag{
				Name:    "restart",
				Usage:   "重启特定服务，参数为服务名称。过程为先停止服务，然后下载升级文件，解压替换，然后启动服务",
				Aliases: []string{"rs"},
			},
		},
	}
	app.Action = func(ctx *cli.Context) error {
		var (
			uri         string
			md5Str      string
			targetDir   string
			err         error
			exePath     string
			serviceName string
			restartServ bool
		)
		exePath, err = os.Executable()
		if err != nil {
			return cli.Exit(fmt.Sprintf("获取文件路径时发生错误： %s", err), 1)
		}
		restartServ = ctx.IsSet("restart")
		if restartServ {
			serviceName = ctx.String("restart")
		}
		if ctx.IsSet("remove") {
			d := ctx.String("remove")
			err = os.Remove(d)
			if err != nil {
				return cli.Exit(fmt.Sprintf("删除文件时发生错误： %s", err), 1)
			}
			return nil
		}
		if ctx.IsSet("list") {
			pluginDir := ctx.String("list")
			if pluginDir == "" {
				pluginDir = filepath.Dir(exePath)
			}
			li, err := getPluginInfoList(pluginDir)
			if err != nil {
				return cli.Exit(fmt.Sprintf("获取插件信息时发生错误： %s", err), 1)
			}
			bs, err := json.Marshal(li)
			if err != nil {
				return cli.Exit(fmt.Sprintf("获取插件信息时发生错误： %s", err), 1)
			}
			w := bufio.NewWriter(os.Stdout)
			w.WriteString(string(bs) + "\n")
			w.Flush()
			return nil
		}
		if ctx.IsSet("file") {
			var fmd string
			fmd, err = utils.GetFileMd5(ctx.String("file"))
			if err != nil {
				return cli.Exit(fmt.Sprintf("计算文件MD5时发生错误： %s", err), 1)
			}
			w := bufio.NewWriter(os.Stdout)
			w.WriteString(fmd + "\n")
			w.Flush()
			return nil
		}
		uri = ctx.String("uri")
		md5Str = ctx.String("md5")
		targetDir = ctx.String("target")
		uri = strings.ReplaceAll(uri, "\\", "/")
		df := filepath.Join(filepath.Dir(exePath), uri[strings.LastIndex(uri, "/")+1:])
		if err = downloadFile(uri, df); err != nil {
			_ = os.Remove(df)
			return cli.Exit(fmt.Sprintf("下载文件时发生错误 %s", err), 1)
		}
		fmd, err := utils.GetFileMd5(df)
		if err != nil {
			_ = os.Remove(df)
			return cli.Exit(fmt.Sprintf("计算文件MD5时发生错误：%s", err), 1)
		}
		if strings.ToLower(fmd) != strings.ToLower(md5Str) {
			_ = os.Remove(df)
			return cli.Exit(fmt.Sprintf("下载文件不完整,下载地址：%s,MD5：%s,下载文件的MD5:%s", uri, md5Str, fmd), 1)
		}
		// 关闭服务
		if restartServ {
			err = stopWinService(serviceName)
			if err != nil {
				return cli.Exit(fmt.Sprintf("停止服务：%s时发生错误：%s", serviceName, err), 1)
			}
		}
		// 解压文件
		if err = Unzip(df, targetDir); err != nil {
			_ = os.Remove(df)
			return cli.Exit(fmt.Sprintf("解压文件时发生错误：%s", err), 1)
		}
		err = os.Remove(df)
		if err != nil {
			return cli.Exit(fmt.Sprintf("删除zip文件时发生错误：%s", err), 1)
		}
		// 启动服务
		if restartServ {
			err = startWinService(serviceName)
			if err != nil {
				return cli.Exit(fmt.Sprintf("启动服务：%s时发生错误：%s", serviceName, err), 1)
			}
		}
		return nil
	}
	if err := app.Run(os.Args); err != nil {
		fmt.Printf("命令行调用错误：%s ，请查看帮助文档", err.Error())
	}
}

func downloadFile(uri string, savePath string) (err error) {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}
	res, err := client.Get(uri)
	if err != nil {
		return
	}
	defer res.Body.Close()
	f, err := os.Create(savePath)
	if err != nil {
		return err
	}
	defer f.Close()
	_, err = io.Copy(f, res.Body)
	return err
}

// Unzip 解压zip文件
func Unzip(zipFile string, destDir string) (err error) {
	zipReader, err := zip.OpenReader(zipFile)
	if err != nil {
		return err
	}
	defer zipReader.Close()
	for _, f := range zipReader.File {
		fpath := filepath.Join(destDir, f.Name)
		if f.FileInfo().IsDir() {
			if err = os.MkdirAll(fpath, os.ModePerm); err != nil {
				return err
			}
		} else {
			if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
				return err
			}
			inFile, err := f.Open()
			if err != nil {
				return err
			}
			outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}
			_, err = io.Copy(outFile, inFile)
			if err != nil {
				return err
			}
			if err := inFile.Close(); err != nil {
				return err
			}
			if err := outFile.Close(); err != nil {
				return err
			}
		}
	}
	return nil
}

// getPluginInfoList 获取插件列表信息
func getPluginInfoList(pluginPath string) (li []PluginInfo, err error) {
	li = make([]PluginInfo, 0)
	entries, err := os.ReadDir(pluginPath)
	if err != nil {
		return
	}
	for _, plu := range entries {
		if !plu.IsDir() {
			filename := plu.Name()
			conf := model.PluginConfig{
				Cmd:             filename,
				Args:            []string{"-v"},
				User:            "",
				Env:             nil,
				TimeoutDuration: 0,
			}
			cr, err := os.Executable()
			if err != nil {
				continue
			}
			// 获取版本信息
			argus := make([]string, 0)
			argus = append(argus, filepath.Join(filepath.Dir(cr), plu.Name()))
			argus = append(argus, conf.Args...)
			stdout, stderr, exitCode, err := cmdutil.RunCommandArgs(argus, cmdutil.CommandOption{})
			if err != nil {
				logrus.Errorf("插件调用时发生错误:[err: %v ],[exitCode:%d],[stdout:%s],[stderr:%s]", err, exitCode, stdout, stderr)
				continue
			}
			m, err := utils.GetFileMd5(filepath.Join(filepath.Dir(cr), plu.Name()))
			if err != nil {
				continue
			}
			item := PluginInfo{
				FileName: filename,
				Md5:      m,
				Version:  strings.ReplaceAll(stdout, "\n", ""),
			}
			li = append(li, item)
		}
	}
	return
}

// stopWinService 停止服务
func stopWinService(servName string) (err error) {
	var (
		m      *mgr.Mgr
		s      *mgr.Service
		status svc.Status
		t1     int64
		t2     int64
	)
	t1 = time.Now().Unix()
	m, err = mgr.Connect()
	if err != nil {
		return
	}
	defer m.Disconnect()
	s, err = m.OpenService(servName)
	if err != nil {
		return
	}
	status, err = s.Control(svc.Stop)
	if err != nil {
		return
	}
	ticker := time.NewTicker(1 * time.Second)
	for range ticker.C {
		status, err = s.Query()
		if err != nil {
			return err
		}
		if status.State == svc.Stopped {
			return
		}
		t2 = time.Now().Unix()
		if t2-t1 > 5 {
			err = errors.New("服务未在规定时间内关闭")
			return err
		}
	}
	return nil
}

// startWinService 启动服务
func startWinService(servName string) (err error) {
	var (
		m *mgr.Mgr
		s *mgr.Service
	)
	m, err = mgr.Connect()
	if err != nil {
		return
	}
	defer m.Disconnect()
	s, err = m.OpenService(servName)
	if err != nil {
		return
	}
	err = s.Start()
	return err
}
